#!/usr/local/bin/perl -w
$help='
##############################################################################
# File:    srec_to_image.pl
# 
# Purpose: Convert an S-record file into various formats for memory images.
# Author:  Quinten Brooks, Freescale TSPG 8/16-Bit DSCO
# Email:   quinten.brooks@freescale.com
# Date:    2003.10.13
##############################################################################
# USAGE[1]:  srec2ver.pl -s [ S-record File ] -a [ Address File ]
#
# USAGE[2]:  srec2ver.pl -s [ S-record File ] -L [ LOD File ]
#
# USAGE[3]:  srec2ver.pl -l [ LOD File ] -a [ Address File ]
# 
# Note: 0x2000000 is used as the S-record Data memory offset.
##############################################################################
';


use constant DEBUG => 0;		# Debugging Messages (0-off, 1-on)
use constant XDATA => 0x02000000;	# S-record Data Memory Marker
#use constant XDATA => 0x00200000;	# S-record Data Memory Marker (old)


%img_ext = ();
%img_addr_hi = ();
%img_addr_lo = ();
%img_type = ();
%img_file = ();
%img_handle = ();
%img_interlaced = ();
%img_even_or_odd = ();
%img_width = ();
%img_order = ();
$fill = "0";


##############################################################################
# getoptions ( patterns, arguments )
#
# In:  $_[0] - String containing patterns for valid dash arguments
#      $_[0]
#       ...  - Array of arguments to process
#      $_[n]
#
# Out: Returns %opt (hash using dash options as keys) and
#              @arg (array containing non-dash arguments w/o duplicates)
#
# Example: getoptions("bs:tw", @arg)
#
##############################################################################
# Note: This subroutine will create the following GLOBAL variables if they 
#       have not been created already:
#
#       %opt     - Hashtable of dash options from command line.
#       @arglist - List of non-dashed options from command line.
##############################################################################
sub getoptions
{
   my ($pattern, @arg) = @_;
   my (@list1, @list2, %sorted, $i, @tmp);
   
   # Get list of valid dash options
   @tmp=split(//, $pattern);
   for ($i=0; $i<@tmp; $i++) {
      if ($tmp[$i+1] && $tmp[$i+1] eq ":") { push(@list2, $tmp[$i]); $i++; }
      else { push(@list1, $tmp[$i]); }
   }

   # Search each command line argument
   foreach ($i=0; $i<@arg; $i++) {
      if ($arg[$i] =~ /^-/) {
   
         # Process dash options that require values
         foreach (@list2) {
            if ($arg[$i] =~ /^-$_/) {
               ($opt{$_} = $arg[$i]) =~ s/^-$_//;
               if (!$opt{$_} && $arg[$i+1] && $arg[$i+1] !~ /^\-/) { $opt{$_} = $arg[$i+1]; $i++; }
               if (!$opt{$_}) { die("Error: Option \"-$_\" requires a value!\n"); }
               last;
            }
         }
      
         # Process dash options that don't require values
         foreach (@list1) { if ($arg[$i] =~ /^-.*$_/) { $opt{$_}=1; } }
      }

      # Ensure each non-dash argument appears only once
      else { $sorted{$arg[$i]} = $arg[$i]; }
   }   
   @arglist = sort(values(%sorted));
   
   # Print debugging info and exit
   if (DEBUG) {
      print "dash options (boolean) = @list1\n";
      print "dash options (value) = @list2\n";
      foreach (sort(keys(%opt))) { print "opt{$_} = $opt{$_}\n"; }
      print "arguments = @arglist\n";
   }
}


##############################################################################
# ProcessAddressFile ()
#
# Use Address File (.addrfile) to get Verilog memory image information.
#
# Input:  $_[0] - Address File
#
# Output: None
##############################################################################
sub ProcessAddressFile
{
   my ($addrfile) = shift(@_);
   my ($line, $comment, @tmp);

   open(IN, "< $addrfile") || die("Error: Unable to open $addrfile!\n");
   while(<IN>) {
      # Skip blank lines, separate text from comments using "#" character
      (/\S/) || next;
      chomp $_;
      $line = $comment = $_;
      $line =~ s/#.*$//g;
      $comment =~ s/^.*#//g;

      # Split read line into the following tokens:
      #   [1] File extension
      #   [2] First address
      #   [3] Last address
      #   [4] Memory Space (0=program, 1=data)
      #   [5] Interlaced (0=no, 1=yes)
      #   [6] Block (0=even, 1=odd)*
      #   [7] Data Width
      #   [8] Data Order (0=normal, 1=reverse)
      #
      # * - This option is only used with interlaced memories.
      @tmp = split(/\s+/, $line);
      if (@tmp >= 6) {
         $img_ext{$tmp[0]} = $tmp[0];
         $img_addr_lo{$tmp[0]} = hex($tmp[1]);
         $img_addr_hi{$tmp[0]} = hex($tmp[2]);
         $img_type{$tmp[0]} = hex($tmp[3]);
         $img_interlaced{$tmp[0]} = hex($tmp[4]);
         $img_even_or_odd{$tmp[0]} = hex($tmp[5]);
	 $img_width{$tmp[0]} = $tmp[6];
	 $img_order{$tmp[0]} = $tmp[7];
         $img_handle{$tmp[0]} = uc($tmp[0]);
      }
   }
   
   if (DEBUG) {
      foreach (sort(keys(%img_ext))) {
         printf("%s\t0x%08x\t0x%08x\t%s\n", $_, $img_addr_lo{$_}, 
	    $img_addr_hi{$_}, $img_handle{$_}, $img_width{$_});
   }}
}


##############################################################################
# GetFileInfo
#
# Separate file name into separate tokens for path, file name, and suffix.
#
# Input:  $_[0] - Filename (including path)
#
# Output: None
##############################################################################
sub GetFileInfo
{
   my ($file, $dir, $base, $ext) = ("", "", "", "");

   ($dir = $_[0]) =~ s/^(.*\/).*$/$1/ if ($_[0] =~ /\//);
   ($file = $_[0]) =~ s/$dir//;
   ($ext  = $file) =~ s/^.\w+// if ($file =~ /\./);
   ($base = $file) =~ s/$ext//;

   if (DEBUG) {
      printf("Info: [%s]\nFile: [%s]\nPath: [%s]\nBase: [%s]\nExt: [%s]\n\n",
         $_[0], $file, $dir, $base, $ext);
   }

   return ($dir, $base, $ext);
}


##############################################################################
# PrintLodData ()
#
# Print a block of data to .lod file.
#
# Input:  $_[0] - .lod file handle
#         $_[1] - Starting address of data block
#         $_[2] - Number of words per row
#         $_[3] - Data word [0]
#          ...
#         $_[n] - Data word [n]
#
# Output: None
##############################################################################
sub PrintLodData
{
   my ($handle, $addr, $cols, @in) = @_;
   my ($i, $j);

   if ($addr < XDATA) { 
      printf($handle "_DATA P %08X\n", $addr);
   }
   else {
      $addr -= XDATA;
      printf($handle "_DATA X %08X\n", $addr);
   }
   OUTER: for ($j=0; $j<@in; $j=$j+$cols) {
      INNER: for ($i=0; $i<$cols; $i++) {
         if (defined($in[$j+$i])) { printf($handle "%04s ", $in[$j+$i]); }
      }					# End of "INNER" loop
      printf($handle "\n");
   }					# End of "OUTER" loop
}


##############################################################################
# PrintImageData ()
#
# Print a block of data to image files.
#
# Input:  $_[0] - Starting address of data block
#         $_[1] - Number of words per row
#         $_[2] - Data word [0]
#          ...
#         $_[n] - Data word [n]
#
# Output: None
##############################################################################
sub PrintImageData
{
   my ($addr, $cols, @in) = @_;
   my ($i, $j, $k, $fh, @tmp);

   LV0:foreach (sort(keys(%img_ext))) {
      $fh = $img_handle{$_};
      $k = ($img_type{$_}) ? $addr - XDATA: $addr;
      if ($k<0 || $k < $img_addr_lo{$_} || $k > $img_addr_hi{$_}) { next; }
      $k = $k - $img_addr_lo{$_};
      @tmp = @in;
      
      if ($img_interlaced{$_}) {
         # Interleaved memory write process (16-bit, odd)
         if ($img_even_or_odd{$_}) {
            if (!($k%2)) { shift(@tmp); $k++; }
            printf($fh "@%X\n", $k>>1);
            LV11:for ($j=0; $j<@tmp; $j+=$cols*2) { LV21:for ($i=0; $i<$cols*2; $i+=2) {
               if (defined($tmp[$j+$i])) { printf($fh "%04s ", $tmp[$j+$i]); }}
            printf($fh "\n");
         }}

         # Interleaved memory write process (16-bit, even)
         else {
            if ($k%2) { shift(@tmp); $k++; }
            printf($fh "@%X\n", $k>>1);
            LV12:for ($j=0; $j<@tmp; $j+=$cols*2) { LV22:for ($i=0; $i<$cols*2; $i+=2) {
               if (defined($tmp[$j+$i])) { printf($fh "%04s ", $tmp[$j+$i]); }}
            printf($fh "\n");
      }}}

      elsif ($img_width{$_} == 32) {
         # Non-interleaved memory write process (32-bit, reverse word)
         if ($img_order{$_}) {
            if ($k%2) { unshift(@tmp, $fill x 4); }
            printf($fh "@%X\n", $k>>1);
            LV13:for ($j=0; $j<@tmp; $j+=$cols*2) { LV23:for ($i=0; $i<$cols*2; $i+=2) {
               if (defined($tmp[$j+$i])) {
                  if (!defined($tmp[$j+$i+1])) { $tmp[$j+$i+1] = $fill x 4; }
                  printf($fh "%04s%04s ", @tmp[$j+$i+1,$j+$i]); }}
               printf($fh "\n");
         }}

         # Non-interleaved memory write process (32-bit)
         else {
            printf($fh "@%X\n", $k);
            LV14:for ($j=0; $j<@in; $j+=$cols) { LV24:for ($i=0; $i<$cols; $i++) {
               if (defined($in[$j+$i])) { printf($fh "%08s ", $in[$j+$i]); }}
               printf($fh "\n");
      }}}

      else {
         # Non-interleaved memory write process (16-bit)
         printf($fh "@%X\n", $k);
         LV15:for ($j=0; $j<@in; $j+=$cols) { LV25:for ($i=0; $i<$cols; $i++) {
            if (defined($in[$j+$i])) { printf($fh "%04s ", $in[$j+$i]); }}
            printf($fh "\n");
   }}}	# End of "LV0"
}


##############################################################################
# ProcessSrecordHeader ()
#
# Extract header information from Header (S0) Records.
#
# Input:  $_[0] - String containing Header S-Record (S0).
#
# Output: $_[0] - String containing Header text.
#
##############################################################################
sub ProcessSrecordHeader
{
   my ($type, $bytes, $addr, $csum, @ascii, $header, $i);
   my (@tmp) = split(//, $_[0]);		# Store characters in array

   $header = "";
   splice(@tmp,0,1);				# "S" marker
   $type = splice(@tmp,0,1);			# S-record type
   $bytes = hex(join("", splice(@tmp,0,2)));	# Byte count
   $addr = hex(join("", splice(@tmp,0,4)));	# Address
   $csum = hex(join("", splice(@tmp,-2,2)));	# Checksum
   for ($i=0; $i<@tmp; $i+=2) { push(@ascii, hex(join("", @tmp[$i,$i+1]))); }
   foreach (@ascii) { $header .= sprintf("%c", $_); }
   if (DEBUG) { printf(STDOUT "Header: %s\n\n", $header); }

   return ($header);
}


##############################################################################
# ProcessSrecordLine ()
#
# Extract address and data from Normal Data S-Records.
#
# Input:  $_[0] - String containing Normal Data S-Record (S1, S2, or S3).
#
# Output: $_[0] - S-Record type
#         $_[1] - Memory space
#         $_[2] - Number of bytes in S-Record
#         $_[3] - Starting address of data block
#         $_[4] - S-Record checksum
#         $_[5] - Data word [0]
#          ...
#         $_[6] - Data word [n]
##############################################################################
sub ProcessSrecordLine
{
   my ($mem, $type, $bytes, $addr, @data, $csum, $i);
   my (@tmp) = split(//, $_[0]);			# Store characters in array
   
   splice(@tmp,0,1);					# "S" marker
   $type = hex(splice(@tmp,0,1));			# S-record type
   $bytes = hex(join("", splice(@tmp,0,2)));		# Byte count
   $addr = hex(join("", splice(@tmp,0,$type*2+2)));	# Address
   $csum = hex(join("", splice(@tmp,-2,2)));		# Checksum
   for ($i=0; $i<@tmp; $i+=4) {
      # Note: Uneven byte counts are handled by the following 2 lines.
      if (!defined($tmp[$i+3])) { $tmp[$i+3]=$fill; }
      if (!defined($tmp[$i+2])) { $tmp[$i+2]=$fill; }
      push(@data, join("", @tmp[$i+2,$i+3,$i,$i+1]));	# Data Bytes
   }
   if (DEBUG) {
      printf(STDOUT "Bytes: %02X, Address: %08X, Checksum: %02X\n", $bytes, $addr, $csum);
      printf(STDOUT "Data: %s\n\n", join(" ", @data));
   }
   $mem = ($addr > XDATA) ? 1: 0;

   return ($type, $mem, $bytes, $addr, $csum, @data);
}


##############################################################################
# SrecordToLod ( srecfile, lodfile )
#
# Translate line in S-record into .lod file data.
# 
# Input:  $_[0] - Input S-record file name
#         $_[1] - Output .lod file name
# 
# Output: Returns 1 if successful, 0 for failure.
##############################################################################
sub SrecordToLod
{
   my ($srecfile, $lodfile) = @_;
   my ($mem, $bytes, $x_addr, $p_addr, $cksm, $header, @data);

   # Open S-record file for input, .lod file for output.
   open(IN, "< $srecfile") || die("Error: Unable to open $srecfile!\n");
   open(OUT, "> $lodfile") || die("Error: Unable to open $lodfile!\n");
   
   # Parse S-record file line by line
   while (<IN>) {
      s/\s//g;			# Remove any whitespace characters from $_
      if (/^S0/) {
         $header = ProcessSrecordHeader($_);
      }
      elsif (/^S1/) {
         ($type, $mem, $bytes, $addr, $cksm, @data) = ProcessSrecordLine($_);
         PrintLodData(OUT, $addr, 8, @data);
      }
      elsif (/^S2/) {
         ($type, $mem, $bytes, $addr, $cksm, @data) = ProcessSrecordLine($_);
         PrintLodData(OUT, $addr, 8, @data);
      }
      elsif (/^S3/) {
         ($type, $mem, $bytes, $addr, $cksm, @data) = ProcessSrecordLine($_);
         PrintLodData(OUT, $addr, 8, @data);
      }
      elsif (/^S4/) { }
      elsif (/^S5/) { }
      elsif (/^S6/) { }
      elsif (/^S7/) { printf(OUT "\n_END 00000000\n"); }
      elsif (/^S8/) { printf(OUT "\n_END 00000000\n"); }
      elsif (/^S9/) { printf(OUT "\n_END 00000000\n"); }
   }
   close OUT;
   close IN;
   
   print STDOUT  "Created .lod file: $lodfile\n";
   return 0;
}


##############################################################################
# SrecordToImage ( srecfile, addrfile )
#
# Translate line in S-record into .lod file data.
# 
# Input:  $_[0] - Input S-record file name
#         $_[1] - Input address file name
# 
# Output: Returns 1 if successful, 0 for failure.
##############################################################################
sub SrecordToImage
{
   my ($srecfile, $addrfile, $img_base) = @_;
   my ($mem, $bytes, $x_addr, $p_addr, $cksm, $header, @data);

   # Extract necessary information from address file
   ProcessAddressFile($addrfile);
   
   # Open S-record and address files for input, image files for output.
   open(IN, "< $srecfile") || die("Error: Unable to open $srecfile!\n");
   $img_base = join("", (GetFileInfo(($opt{b}) ? $opt{b}: $srecfile))[0,1]);
   foreach (keys(%img_ext)) {
      $img_file{$_} = "$img_base.$img_ext{$_}";
      open($img_handle{$_}, "> $img_file{$_}") || die("Error: Unable to open $img_file{$_}\n");
   }
   
   # Parse S-record file line by line
   while (<IN>) {
      s/\s//g;			# Remove any whitespace characters from $_
      if (/^S0/) {
         $header = ProcessSrecordHeader($_);
      }
      elsif (/^S1/) {
         ($type, $mem, $bytes, $addr, $cksm, @data) = ProcessSrecordLine($_);
         PrintImageData($addr, 8, @data);
      }
      elsif (/^S2/) {
         ($type, $mem, $bytes, $addr, $cksm, @data) = ProcessSrecordLine($_);
         PrintImageData($addr, 8, @data);
      }
      elsif (/^S3/) {
         ($type, $mem, $bytes, $addr, $cksm, @data) = ProcessSrecordLine($_);
         PrintImageData($addr, 8, @data);
      }
      elsif (/^S4/) { }
      elsif (/^S5/) { }
      elsif (/^S6/) { }
      elsif (/^S7/) { }
      elsif (/^S8/) { }
      elsif (/^S9/) { }
   }
   close IN;
   foreach (sort(keys(%img_ext))) {
      close $img_handle{$_};
      print STDOUT  "Created image file: $img_file{$_}\n";
   }
   
   return 0;
}


##############################################################################
# LodToImage (lodfile, addrfile)
##############################################################################
sub LodToImage
{
   my ($lodfile, $addrfile, $img_base) = @_;
   my ($mem, $bytes, $x_addr, $p_addr, $cksm, $header, @data);

   # Extract necessary information from address file
   ProcessAddressFile($addrfile);
   
   # Open .lod file for input, image files for output.
   open(IN, "< $lodfile") || die("Error: Unable to open $lodfile!\n");
   $img_base = join("", (get_file_info($lodfile))[0,1]);
   foreach (keys(%img_ext)) {
      $img_file{$_} = "$img_base.$img_ext{$_}";
      open($img_handle{$_}, "> $img_file{$_}") || die("Error: Unable to open $img_file{$_}\n");
   }
   
   # Parse .lod file line by line
   @data=();
   while (<IN>) {
      if (!/\S/) { next; }	# Skip line if all whitespace characters
      chomp;			# Remove <CR/LF> from line
      @tmp = split(/\s+/, $_);	# Split line into tokens using whitespace
      if (/DATA/) {
         if (@data) { PrintImageData($addr, 8, @data); @data=(); }
         $addr = ($tmp[1] =~ /[Xx]/) ? XDATA: 0;
	 $addr += hex($tmp[2]);
      }
      elsif (/END/) {
         if (@data) { PrintImageData($addr, 8, @data); @data=(); }
	 last;
      }
      else { push(@data, @tmp); }
   }
   close IN;
   foreach (sort(keys(%img_ext))) {
      close $img_handle{$_};
      print STDOUT  "Created image file: $img_file{$_}\n";
   }
   return 0;
}


##############################################################################
# M A I N
##############################################################################
# Process command line dash options and arguments
getoptions("a:b:l:s:L:", @ARGV);

# Use S-record file to create .lod file.
if ($opt{L}) { SrecordToLod($opt{s}, $opt{L}); }

# Use S-record file and address file to create Verilog image files.
elsif ($opt{s}) { SrecordToImage($opt{s}, $opt{a}); }

# Use .lod file and address file to create Verilog image files.
elsif ($opt{l}) { LodToImage($opt{l}, $opt{a}); }

# Print USAGE info if invalid arguments supplied to command line
else { print $help; }

############################  END OF PROGRRAM  ###############################
__END__
